#region using statements

using System;
using System.Collections.Generic;
using System.Linq;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

using NGen.Managers;
using NGen.Managers.Logging;

#endregion

namespace NGen
{
	/// <summary>
	/// 	This class provides access to the spritebatch, components, services, 
	/// 	content manager and graphics device manager
	/// </summary>
	public sealed class GameEngine : Game
	{
		#region static fields

		/// <summary>
		/// 	maintains a list of managers used by the engine
		/// </summary>
		private static List<GameManager> s_clManagersOrderedByUpdate = new List<GameManager>();

		private static List<GameManager> s_clManagersOrderedByDraw = new List<GameManager>();

		#endregion

		#region static properties

#if DEBUG
		public const LoggingLevel DefaultLoggingLevel = LoggingLevel.MINIMAL;
#else
		public const LoggingLevel DefaultLoggingLevel = LoggingLevel.NONE;
#endif

		/// <summary>
		/// 	access to a random value generator
		/// </summary>
		public static Random Random { get; private set; }

		/// <summary>
		/// 	access to the SpriteBatch
		/// </summary>
		public static SpriteBatch SpriteBatch { get; private set; }

		/// <summary>
		/// 	access to the GameServiceContainer
		/// </summary>
		public static GameServiceContainer GameServices { get; private set; }

		/// <summary>
		/// 	access to the GameComponentsCollection
		/// </summary>
		public static GameComponentCollection GameComponents { get; private set; }

		/// <summary>
		/// 	access to the ContentManager
		/// </summary>
		public static ContentManager GameContent { get; private set; }

		/// <summary>
		/// 	access to the GraphicsDeviceManager
		/// </summary>
		public static GraphicsDeviceManager GraphicsDeviceManager { get; private set; }

		/// <summary>
		/// 	access to the GraphicsDevice
		/// </summary>
		public new static GraphicsDevice GraphicsDevice {
			get { return GraphicsDeviceManager.GraphicsDevice; }
		}

		/// <summary>
		/// 	access to the GameWindow
		/// </summary>
		public static GameWindow GameWindow { get; private set; }

		/// <summary>
		/// 	access to the output to the primary logger
		/// </summary>
		public static ILogger Logger { get; private set; }

		#endregion

		#region constructors

		/// <summary>
		/// 	Static constructor initializes random value generator
		/// </summary>
		static GameEngine() {
			Random = new Random(DateTime.Now.Millisecond);
		}

		/// <summary>
		/// 	Initializes static members and instantiates the GameStateManager
		/// </summary>
		public GameEngine() {
			GameServices = Services;
			GameContent = Content;
			GameComponents = Components;
			GraphicsDeviceManager = new GraphicsDeviceManager(this);
			GameWindow = Window;
			IsFixedTimeStep = false;
			GameServices.AddService(typeof(IManagerLogs), new ManagerLogs());
			GetService<IManagerLogs>().GlobalLoggingLevel = DefaultLoggingLevel;
			Logger = GetService<IManagerLogs>().Log<GameEngine>();

			Log("GameEngine created");
		}

		#endregion

		#region local methods

		/// <summary>
		/// 	initialize the game engine and all managers
		/// </summary>
		protected override void Initialize() {
			GameServices.AddService(typeof(Game), this);

			SpriteBatch = new SpriteBatch(GraphicsDevice);
			foreach(var manager in s_clManagersOrderedByUpdate) {
				manager.Initialize();
				Log(Logger.BuildString(manager.GetType().Name).Append(" initialized").ToString());
			}

			base.Initialize();
			Log("GameEngine initialized");
		}

		/// <summary>
		/// 	update all managers
		/// </summary>
		/// <param name = "p_gameTime">snapshot of timing values</param>
		protected override void Update(GameTime p_gameTime) {
			foreach(var manager in s_clManagersOrderedByUpdate) {
				if(!manager.SkipUpdate) {
					manager.Update(p_gameTime);
				}
			}
		}

		/// <summary>
		/// 	draw all managers
		/// </summary>
		/// <param name = "p_gameTime">snapshot of timing values</param>
		protected override void Draw(GameTime p_gameTime) {
			GraphicsDevice.Clear(Color.Transparent);
			foreach(var manager in s_clManagersOrderedByDraw) {
				if(!manager.SkipDraw) {
					manager.Draw(p_gameTime);
				}
			}

			base.Draw(p_gameTime);
		}

		/// <summary>
		/// 	cleans up and destroys all managers
		/// </summary>
		protected override void EndRun() {
			foreach(var manager in s_clManagersOrderedByUpdate) {
				manager.Cleanup();
			}

			s_clManagersOrderedByUpdate.Clear();
			s_clManagersOrderedByDraw.Clear();

			base.EndRun();
			Log("GameEngine cleaned up");
			GetService<IManagerLogs>().Cleanup();
		}

		#endregion

		#region static methods

		/// <summary>
		/// 	immediately exits the running game.
		/// </summary>
		public new static void Exit() {
			GetService<Game>().Exit();
			Log("GameEngine exiting");
		}

		/// <summary>
		/// 	<para>provides simple access for getting services. The returned service
		/// 		is already type casted to the specified type.</para>
		/// 	<para>EXAMPLE: GameEngine.GetService<IManagerInput>()</para>
		/// </summary>
		/// <typeparam name = "T">the type of service to return</typeparam>
		/// <returns>the service specified in the type parameter</returns>
		public static T GetService<T>() {
			return (T) GameServices.GetService(typeof(T));
		}

		/// <summary>
		/// 	adds a manager to the manager to the game engine
		/// </summary>
		/// <param name = "p_manager">the manager to add</param>
		public static void AddManager(object p_manager) {
			if(!(p_manager is GameManager)) {
				throw new Exception("Manager passed in does not inherit from abstract class GameManager");
			}

			s_clManagersOrderedByUpdate.Add(p_manager as GameManager);
			s_clManagersOrderedByDraw.Add(p_manager as GameManager);
			Log(Logger.BuildString("Manager added to engine: ").Append(p_manager.GetType().Name).ToString());
		}

		/// <summary>
		/// 	removes a manager from the manager to the game engine
		/// </summary>
		/// <param name = "p_manager">the manager to remove</param>
		public static void RemoveManager(object p_manager) {
			if(!(p_manager is GameManager)) {
				throw new Exception("Manager passed in does not inherit from abstract class GameManager");
			}

			s_clManagersOrderedByUpdate.Remove(p_manager as GameManager);
			s_clManagersOrderedByDraw.Remove(p_manager as GameManager);

			Log(Logger.BuildString("Manager removed from engine: ").Append(p_manager.GetType().Name).ToString());
		}

		/// <summary>
		/// 	log a message to the primary log file
		/// </summary>
		/// <param name = "p_message">message to log</param>
		/// <param name = "p_logLevel">logging level to use</param>
		public static void Log(string p_message, LoggingLevel p_logLevel = DefaultLoggingLevel) {
			Logger.Write(p_message, p_logLevel);
		}

		/// <summary>
		/// 	log a message to a specified logfile
		/// </summary>
		/// <typeparam name = "TLoggerType">the logger to use</typeparam>
		/// <param name = "p_message">message to log</param>
		/// <param name = "p_logLevel">logging level to use</param>
		public static void Log<TLoggerType>(string p_message, LoggingLevel p_logLevel = DefaultLoggingLevel) {
			GetService<IManagerLogs>().Log<TLoggerType>(p_message, p_logLevel);
		}

		public static void UpdateOrDrawOrderChanged() {
			s_clManagersOrderedByUpdate = s_clManagersOrderedByUpdate.OrderBy(p_l => p_l.UpdateOrder).ToList();
			s_clManagersOrderedByDraw = s_clManagersOrderedByDraw.OrderBy(p_l => p_l.DrawOrder).ToList();
		}

		#endregion
	}
}